﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.ComponentModel;
using System.Web.Mvc;

namespace Gama.Extension
{
    public static partial class HtmlHelperExtensions
    {
        private const string IListIndexerGetMethod = "get_Item";
        private const string MultiDimensionArrayIndexerGetMethod = "Get";

        /// <summary>
        /// Builds binding path for given expression
        /// </summary>
        /// <typeparam name="TModel">Model type</typeparam>
        /// <typeparam name="TModel">Expression type</typeparam>
        /// <param name="htmlHelper">Self</param>
        /// <param name="expression">Expression of path</param>
        /// <param name="indices">Index values for non-constant indexers</param>
        /// <returns>Binding path</returns>
        internal static string BuildBindingPath<TModel, R>(this HtmlHelper htmlHelper, Expression<Func<TModel, R>> expression)
        {
            StringBuilder path = new StringBuilder();

            Expression innerExpression = expression.Body;

            bool buildingFinished = false;

            while (!buildingFinished)
            {
                switch (innerExpression.NodeType)
                {
                    case ExpressionType.ArrayIndex: // 1-dimension array indexer

                        BinaryExpression be = innerExpression as BinaryExpression;

                        object index = Expression.Lambda(be.Right).Compile().DynamicInvoke();

                        path.Insert(0, String.Format("[{0}]", index));

                        innerExpression = be.Left;

                        break;

                    case ExpressionType.Call: // n-dimension array indexer OR IList indexer

                        MethodCallExpression mce = innerExpression as MethodCallExpression;

                        if (
                            // IList indexer
                            (mce.Method.IsSpecialName && mce.Method.Name.Equals(IListIndexerGetMethod, StringComparison.OrdinalIgnoreCase)) ||
                            // OR n-dimension array indexer
                            (mce.Object.Type.IsArray && mce.Method.Name.EndsWith(MultiDimensionArrayIndexerGetMethod, StringComparison.OrdinalIgnoreCase))
                            )
                        {
                            IList<object> indexValues = new List<object>();

                            // supports multiple indexers :-)
                            foreach (Expression argument in mce.Arguments)
                            {
                                indexValues.Add(Expression.Lambda(argument, null).Compile().DynamicInvoke(null));
                            }

                            path.Insert(0, String.Format("[{0}]", String.Join(", ", indexValues.Select(value => value.ToString()).ToArray())));
                        }

                        innerExpression = mce.Object;

                        break;

                    case ExpressionType.MemberAccess:

                        MemberExpression me = innerExpression as MemberExpression;

                        path.Insert(0, String.Format(".{0}", me.Member.Name));

                        innerExpression = me.Expression;

                        break;

                    default:

                        if (innerExpression is UnaryExpression) // just get past UnaryExpression
                        {
                            innerExpression = ((UnaryExpression)innerExpression).Operand;

                            break;
                        }
                        
                        buildingFinished = true;

                        break;
                }
            }            

            // remove leading dot if any
            if (path.Length > 0)
            {
                path.Replace(".", String.Empty, 0, 1);
            }

            // add prefix if any
            string prefix = BindingSection.GetCurrentBindingPrefix(GetBindingSectionContext(htmlHelper));

            if (prefix != String.Empty)
            {
                path.Insert(0, prefix + (path.ToString().StartsWith("[") ? String.Empty : "."));
            }            

            return path.ToString();
        }
    }
}
